/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.ryan.ohos.extension.nested.component;

import com.ryan.ohos.extension.ComponentPositionHelper;
import com.ryan.ohos.extension.EstimateHelper;
import com.ryan.ohos.extension.MathUtils;
import com.ryan.ohos.extension.nested.NestedScrollingChild;
import com.ryan.ohos.extension.nested.NestedScrollingHelper;
import com.ryan.ohos.extension.nested.component.coordinatorlayout.CoordinatorLayout;
import com.ryan.ohos.extension.nested.component.coordinatorlayout.behavior.scrolling.HeaderBehavior;
import com.ryan.ohos.extension.nested.component.coordinatorlayout.behavior.scrolling.HeaderScrollingViewBehavior;
import com.ryan.ohos.extension.scroller.animation.Interpolator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.*;
import ohos.agp.utils.Rect;
import ohos.app.Context;
import ohos.utils.Sequenceable;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

/**
 * AppBarLayout is a vertical {@link DirectionalLayout} which implements many of the features of material
 * designs app bar concept, namely scrolling gestures.
 *
 * <p>Children should provide their desired scrolling behavior through {@link
 * LayoutParams#setScrollFlags(int)} and the associated layout xml attribute: {@code
 * app:layout_scrollFlags}.
 *
 * <p>This view depends heavily on being used as a direct child within a {@link CoordinatorLayout}.
 * If you use AppBarLayout within a different {@link ComponentContainer}, most of its functionality will not
 * work.
 *
 * <p>AppBarLayout also requires a separate scrolling sibling in order to know when to scroll. The
 * binding is done through the {@link ScrollingViewBehavior} behavior class, meaning that you should
 * set your scrolling view's behavior to be an instance of {@link ScrollingViewBehavior}. A string
 * resource containing the full class name is available.
 */
public class AppBarLayout extends DirectionalLayout implements
        Component.EstimateSizeListener, ComponentContainer.ArrangeListener, CoordinatorLayout.AttachedBehavior {
    static final int PENDING_ACTION_NONE = 0x0;
    static final int PENDING_ACTION_EXPANDED = 0x1;
    static final int PENDING_ACTION_COLLAPSED = 0x2;
    static final int PENDING_ACTION_ANIMATE_ENABLED = 0x4;
    static final int PENDING_ACTION_FORCE = 0x8;

    /**
     * Interface definition for a callback to be invoked when an {@link AppBarLayout}'s vertical
     * offset changes.
     */
    public interface BaseOnOffsetChangedListener<T extends AppBarLayout> {

        /**
         * Called when the {@link AppBarLayout}'s layout offset has been changed. This allows child
         * views to implement custom behavior based on the offset (for instance pinning a view at a
         * certain y value).
         *
         * @param appBarLayout   the {@link AppBarLayout} which offset has changed
         * @param verticalOffset the vertical offset for the parent {@link AppBarLayout}, in px
         */
        void onOffsetChanged(T appBarLayout, int verticalOffset);
    }

    /**
     * Interface definition for a callback to be invoked when an {@link AppBarLayout}'s vertical
     * offset changes.
     */
    public interface OnOffsetChangedListener extends BaseOnOffsetChangedListener<AppBarLayout> {
        void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset);
    }

    private static final int INVALID_SCROLL_RANGE = -1;

    private int currentOffset;
    private int totalScrollRange = INVALID_SCROLL_RANGE;
    private int downPreScrollRange = INVALID_SCROLL_RANGE;
    private int downScrollRange = INVALID_SCROLL_RANGE;

    private boolean haveChildWithInterpolator;
    private boolean isLaidOut;

    private int pendingAction = PENDING_ACTION_NONE;

//    private WindowInsetsCompat mLastInsets;

    /**
     * @noinspection rawtypes
     **/
    private List<BaseOnOffsetChangedListener> listeners;

//    private boolean liftOnScroll;
//    private int liftOnScrollTargetViewId;
//    private WeakReference<Component> liftOnScrollTargetView;
//    private AnimatorValue elevationOverlayAnimator;
//
//    private int[] tmpStatesArray;

    private boolean mCollapsible;
    private boolean mCollapsed;

//    private Drawable statusBarForeground;

    public AppBarLayout(Context context) {
        this(context, null);
    }

    public AppBarLayout(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
    }

    public AppBarLayout(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);

        // TODO apply themes

        setOrientation(VERTICAL);
        setEstimateSizeListener(this);
        setArrangeListener(this);
    }

    /**
     * Add a listener that will be called when the offset of this {@link AppBarLayout} changes.
     *
     * @param listener The listener that will be called when the offset changes.]
     * @noinspection rawtypes
     * @see #removeOnOffsetChangedListener(OnOffsetChangedListener)
     */
    public void addOnOffsetChangedListener(BaseOnOffsetChangedListener listener) {
        if (listeners == null) {
            listeners = new ArrayList<>();
        }
        if (listener != null && !listeners.contains(listener)) {
            listeners.add(listener);
        }
    }

    /**
     * @noinspection rawtypes
     */
    public void addOnOffsetChangedListener(OnOffsetChangedListener listener) {
        addOnOffsetChangedListener((BaseOnOffsetChangedListener) listener);
    }

    /**
     * Remove the previously added {@link OnOffsetChangedListener}.
     *
     * @param listener the listener to remove.
     * @noinspection rawtypes
     */
    public void removeOnOffsetChangedListener(BaseOnOffsetChangedListener listener) {
        if (listeners != null && listener != null) {
            listeners.remove(listener);
        }
    }

    /**
     * @noinspection rawtypes
     */
    public void removeOnOffsetChangedListener(OnOffsetChangedListener listener) {
        removeOnOffsetChangedListener((BaseOnOffsetChangedListener) listener);
    }

    @Override
    public boolean onEstimateSize(int i, int i1) {
        invalidateScrollRanges();
        return false;
    }

    @Override
    public boolean onArrange(int left, int top, int width, int height) {
        invalidateScrollRanges();

        haveChildWithInterpolator = false;
        for (int i = 0, z = getChildCount(); i < z; i++) {
            final Component child = getComponentAt(i);
            final LayoutParams childLp = (LayoutParams) child.getLayoutConfig();
            final Interpolator interpolator = childLp.getScrollInterpolator();

            if (interpolator != null) {
                haveChildWithInterpolator = true;
                break;
            }
        }

        isLaidOut = true;

//        updateCollapsible();
        return false;
    }

    private void updateCollapsible() {
        boolean haveCollapsibleChild = false;
        for (int i = 0, z = getChildCount(); i < z; i++) {
            if (((LayoutParams) getComponentAt(i).getLayoutConfig()).isCollapsible()) {
                haveCollapsibleChild = true;
                break;
            }
        }
        setCollapsibleState(haveCollapsibleChild);
    }

    private boolean hasCollapsibleChild() {
        for (int i = 0, z = getChildCount(); i < z; i++) {
            if (((LayoutParams) getComponentAt(i).getLayoutConfig()).isCollapsible()) {
                return true;
            }
        }
        return false;
    }

    private void invalidateScrollRanges() {
        // Invalidate the scroll ranges
        totalScrollRange = INVALID_SCROLL_RANGE;
        downPreScrollRange = INVALID_SCROLL_RANGE;
        downScrollRange = INVALID_SCROLL_RANGE;
    }

    @Override
    public void setOrientation(int orientation) {
        if (orientation != VERTICAL) {
            throw new IllegalArgumentException("AppBarLayout is always vertical and does"
                    + " not support horizontal orientation");
        }
        super.setOrientation(orientation);
    }

    @Override
    public CoordinatorLayout.Behavior<AppBarLayout> getBehavior() {
        return new AppBarLayout.Behavior();
    }

    /**
     * Sets whether this {@link AppBarLayout} is expanded or not, animating if it has already
     * been laid out.
     *
     * <p>As with {@link AppBarLayout}'s scrolling, this method relies on this layout being a
     * direct child of a {@link CoordinatorLayout}.</p>
     *
     * @param expanded true if the layout should be fully expanded, false if it should
     *                 be fully collapsed
     */
    public void setExpanded(boolean expanded) {
        setExpanded(expanded, isLaidOut);
    }

    /**
     * Sets whether this {@link AppBarLayout} is expanded or not.
     *
     * <p>As with {@link AppBarLayout}'s scrolling, this method relies on this layout being a
     * direct child of a {@link CoordinatorLayout}.</p>
     *
     * @param expanded true if the layout should be fully expanded, false if it should
     *                 be fully collapsed
     * @param animate  Whether to animate to the new state
     * @attr ref android.support.design.R.styleable#AppBarLayout_expanded
     */
    public void setExpanded(boolean expanded, boolean animate) {
        setExpanded(expanded, animate, true);
    }

    private void setExpanded(boolean expanded, boolean animate, boolean force) {
        pendingAction = (expanded ? PENDING_ACTION_EXPANDED : PENDING_ACTION_COLLAPSED)
                | (animate ? PENDING_ACTION_ANIMATE_ENABLED : 0)
                | (force ? PENDING_ACTION_FORCE : 0);
        postLayout();
    }

//    @Override
//    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
//        return p instanceof LayoutParams;
//    }
//
//    @Override
//    protected LayoutParams generateDefaultLayoutParams() {
//        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
//    }


    @Override
    public ComponentContainer.LayoutConfig createLayoutConfig(Context context, AttrSet attrSet) {
        return new LayoutParams(getContext(), attrSet);
    }

    @Override
    public ComponentContainer.LayoutConfig verifyLayoutConfig(ComponentContainer.LayoutConfig config) {
        if (config instanceof LayoutParams) return config;
        return new LayoutParams(config);
    }

    protected boolean hasChildWithInterpolator() {
        return haveChildWithInterpolator;
    }

    /**
     * Returns the scroll range of all children.
     *
     * @return the scroll range in px
     */
    public final int getTotalScrollRange() {
        if (totalScrollRange != INVALID_SCROLL_RANGE) {
            return totalScrollRange;
        }

        int range = 0;
        for (int i = 0, z = getChildCount(); i < z; i++) {
            final Component child = getComponentAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutConfig();
            final int childHeight = child.getEstimatedHeight();
            final int flags = lp.scrollFlags;

            if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
                // We're set to scroll so add the child's height
                range += childHeight + lp.getMarginTop() + lp.getMarginBottom();

                if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
                    // For a collapsing scroll, we to take the collapsed height into account.
                    // We also break straight away since later views can't scroll beneath
                    // us
                    range -= child.getMinHeight();
                    break;
                }
            } else {
                // As soon as a view doesn't have the scroll flag, we end the range calculation.
                // This is because views below can not scroll under a fixed view.
                break;
            }
        }
        return totalScrollRange = range; // Math.max(0, range - getTopInset());
    }

    boolean hasScrollableChildren() {
        return getTotalScrollRange() != 0;
    }

    /**
     * Return the scroll range when scrolling up from a nested pre-scroll.
     */
    int getUpNestedPreScrollRange() {
        return getTotalScrollRange();
    }

    /**
     * Return the scroll range when scrolling down from a nested pre-scroll.
     */
    int getDownNestedPreScrollRange() {
        if (downPreScrollRange != INVALID_SCROLL_RANGE) {
            // If we already have a valid value, return it
            return downPreScrollRange;
        }

        int range = 0;
        for (int i = getChildCount() - 1; i >= 0; i--) {
            final Component child = getComponentAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutConfig();
            final int childHeight = child.getEstimatedHeight();
            final int flags = lp.scrollFlags;

            if ((flags & LayoutParams.FLAG_QUICK_RETURN) == LayoutParams.FLAG_QUICK_RETURN) {
                // First take the margin into account
                range += lp.getMarginTop() + lp.getMarginBottom();
                // The view has the quick return flag combination...
                if ((flags & LayoutParams.SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED) != 0) {
                    // If they're set to enter collapsed, use the minimum height
                    range += child.getMinHeight();
                } else if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
                    // Only enter by the amount of the collapsed height
                    range += childHeight - child.getMinHeight();
                } else {
                    // Else use the full height (minus the top inset)
                    range += childHeight;
                }
            } else if (range > 0) {
                // If we've hit an non-quick return scrollable view, and we've already hit a
                // quick return view, return now
                break;
            }
        }
        return downPreScrollRange = Math.max(0, range);
    }

    /**
     * Return the scroll range when scrolling down from a nested scroll.
     */
    int getDownNestedScrollRange() {
        if (downScrollRange != INVALID_SCROLL_RANGE) {
            // If we already have a valid value, return it
            return downScrollRange;
        }

        int range = 0;
        for (int i = 0, z = getChildCount(); i < z; i++) {
            final Component child = getComponentAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutConfig();
            int childHeight = child.getEstimatedHeight();
            childHeight += lp.getMarginTop() + lp.getMarginBottom();

            final int flags = lp.scrollFlags;

            if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
                // We're set to scroll so add the child's height
                range += childHeight;

                if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
                    // For a collapsing exit scroll, we to take the collapsed height into account.
                    // We also break the range straight away since later views can't scroll
                    // beneath us
                    range -= child.getMinHeight();
                    break;
                }
            } else {
                // As soon as a view doesn't have the scroll flag, we end the range calculation.
                // This is because views below can not scroll under a fixed view.
                break;
            }
        }
        return downScrollRange = Math.max(0, range);
    }

//    void dispatchOffsetUpdates(int offset) {
//        // Iterate backwards through the list so that most recently added listeners
//        // get the first chance to decide
//        if (listeners != null) {
//            for (int i = 0, z = listeners.size(); i < z; i++) {
//                final OnOffsetChangedListener listener = listeners.get(i);
//                if (listener != null) {
//                    listener.onOffsetChanged(this, offset);
//                }
//            }
//        }
//    }

    /**
     * @noinspection rawtypes
     */
    void onOffsetChanged(int offset) {
        currentOffset = offset;

//        if (!willNotDraw()) {
//            ViewCompat.postInvalidateOnAnimation(this);
//        }

        // Iterate backwards through the list so that most recently added listeners
        // get the first chance to decide
        if (listeners != null) {
            for (int i = 0, z = listeners.size(); i < z; i++) {
                final BaseOnOffsetChangedListener listener = listeners.get(i);
                if (listener != null) {
                    listener.onOffsetChanged(this, offset);
                }
            }
        }
    }

    final int getMinimumHeightForVisibleOverlappingContent() {
        final int topInset = 0;
        final int minHeight = getMinHeight();
        if (minHeight != 0) {
            // If this layout has a min height, use it (doubled)
            return (minHeight * 2) + topInset;
        }

        // Otherwise, we'll use twice the min height of our last child
        final int childCount = getChildCount();
        final int lastChildMinHeight = childCount >= 1
                ? getComponentAt(childCount - 1).getMinHeight() : 0;
        if (lastChildMinHeight != 0) {
            return (lastChildMinHeight * 2) + topInset;
        }

        // If we reach here then we don't have a min height explicitly set. Instead we'll take a
        // guess at 1/3 of our height being visible
        return getHeight() / 3;
    }

//    @Override
//    protected int[] onCreateDrawableState(int extraSpace) {
//        final int[] extraStates = mTmpStatesArray;
//        final int[] states = super.onCreateDrawableState(extraSpace + extraStates.length);
//
//        extraStates[0] = mCollapsible ? R.attr.state_collapsible : -R.attr.state_collapsible;
//        extraStates[1] = mCollapsible && mCollapsed
//                ? R.attr.state_collapsed : -R.attr.state_collapsed;
//
//        return mergeDrawableStates(states, extraStates);
//    }

    /**
     * Sets whether the AppBarLayout has collapsible children or not.
     *
     * @return true if the collapsible state changed
     */
    private boolean setCollapsibleState(boolean collapsible) {
        if (mCollapsible != collapsible) {
            mCollapsible = collapsible;
//            refreshDrawableState();
            return true;
        }
        return false;
    }

    /**
     * Sets whether the AppBarLayout is in a collapsed state or not.
     *
     * @return true if the collapsed state changed
     */
    boolean setCollapsedState(boolean collapsed) {
        if (mCollapsed != collapsed) {
            mCollapsed = collapsed;
//            refreshDrawableState();
            return true;
        }
        return false;
    }

    int getPendingAction() {
        return pendingAction;
    }

    void resetPendingAction() {
        pendingAction = PENDING_ACTION_NONE;
    }

//    WindowInsetsCompat onWindowInsetChanged(final WindowInsetsCompat insets) {
//        WindowInsetsCompat newInsets = null;
//
//        if (ViewCompat.getFitsSystemWindows(this)) {
//            // If we're set to fit system windows, keep the insets
//            newInsets = insets;
//        }
//
//        // If our insets have changed, keep them and invalidate the scroll ranges...
//        if (!objectEquals(mLastInsets, newInsets)) {
//            mLastInsets = newInsets;
//            invalidateScrollRanges();
//        }
//
//        return insets;
//    }

    public static class LayoutParams extends LayoutConfig {

        /**
         * Disable scrolling on the view. This flag should not be combined with any of the other scroll
         * flags.
         */
        public static final int SCROLL_FLAG_NO_SCROLL = 0x0;

        /**
         * The view will be scroll in direct relation to scroll events. This flag needs to be set for
         * any of the other flags to take effect. If any sibling views before this one do not have this
         * flag, then this value has no effect.
         */
        public static final int SCROLL_FLAG_SCROLL = 0x1;

        /**
         * When exiting (scrolling off screen) the view will be scrolled until it is 'collapsed'. The
         * collapsed height is defined by the view's minimum height.
         */
        public static final int SCROLL_FLAG_EXIT_UNTIL_COLLAPSED = 1 << 1;

        /**
         * When entering (scrolling on screen) the view will scroll on any downwards scroll event,
         * regardless of whether the scrolling view is also scrolling. This is commonly referred to as
         * the 'quick return' pattern.
         */
        public static final int SCROLL_FLAG_ENTER_ALWAYS = 1 << 2;

        /**
         * An additional flag for 'enterAlways' which modifies the returning view to only initially
         * scroll back to it's collapsed height. Once the scrolling view has reached the end of it's
         * scroll range, the remainder of this view will be scrolled into view. The collapsed height is
         * defined by the view's minimum height.
         */
        public static final int SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED = 1 << 3;

        /**
         * Upon a scroll ending, if the view is only partially visible then it will be snapped and
         * scrolled to its closest edge. For example, if the view only has its bottom 25% displayed, it
         * will be scrolled off screen completely. Conversely, if its bottom 75% is visible then it will
         * be scrolled fully into view.
         */
        public static final int SCROLL_FLAG_SNAP = 1 << 4;

        /**
         * An additional flag to be used with 'snap'. If set, the view will be snapped to its top and
         * bottom margins, as opposed to the edges of the view itself.
         */
        public static final int SCROLL_FLAG_SNAP_MARGINS = 1 << 5;

        /**
         * Internal flags which allows quick checking features
         */
        static final int FLAG_QUICK_RETURN = SCROLL_FLAG_SCROLL | SCROLL_FLAG_ENTER_ALWAYS;

        static final int FLAG_SNAP = SCROLL_FLAG_SCROLL | SCROLL_FLAG_SNAP;
        static final int COLLAPSIBLE_FLAGS =
                SCROLL_FLAG_EXIT_UNTIL_COLLAPSED | SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED;

        int scrollFlags = SCROLL_FLAG_SCROLL;
        Interpolator scrollInterpolator;

        public LayoutParams(Context c, AttrSet attrs) {
            super(c, attrs);

            String flags = attrs.getAttr("layout_scrollFlags").map(Attr::getStringValue).orElse("");
            scrollFlags = createScrollFlags(flags);
//            if (a.hasValue(R.styleable.AppBarLayout_Layout_layout_scrollInterpolator)) {
//                int resId = a.getResourceId(
//                        R.styleable.AppBarLayout_Layout_layout_scrollInterpolator, 0);
//                mScrollInterpolator = android.view.animation.AnimationUtils.loadInterpolator(
//                        c, resId);
//            }
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(int width, int height, int alignment, float weight) {
            super(width, height, alignment, weight);
        }

        public LayoutParams(ComponentContainer.LayoutConfig p) {
            super(p);
        }

        public LayoutParams(LayoutConfig source) {
            super(source);
        }

        public LayoutParams(LayoutParams source) {
            super(source);
            scrollFlags = source.scrollFlags;
            scrollInterpolator = source.scrollInterpolator;
        }

        private int createScrollFlags(String flagStr) {
            if (flagStr == null || flagStr.length() <= 0) return 0;
            String[] flags = flagStr.split("\\|");

            int result = 0;
            for (String flag : flags) {
                switch (flag) {
                    case "scroll":
                        result |= SCROLL_FLAG_SCROLL;
                        break;
                    case "exitUntilCollapsed":
                        result |= SCROLL_FLAG_EXIT_UNTIL_COLLAPSED;
                        break;
                    case "enterAlways":
                        result |= SCROLL_FLAG_ENTER_ALWAYS;
                        break;
                    case "enterAlwaysCollapsed":
                        result |= SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED;
                        break;
                    case "snap":
                        result |= SCROLL_FLAG_SNAP;
                        break;
                }
            }

            return result;
        }

        /**
         * Set the scrolling flags.
         *
         * @param flags bitwise int of {@link #SCROLL_FLAG_SCROLL}, {@link
         *              #SCROLL_FLAG_EXIT_UNTIL_COLLAPSED}, {@link #SCROLL_FLAG_ENTER_ALWAYS}, {@link
         *              #SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED}, {@link #SCROLL_FLAG_SNAP}, and {@link
         *              #SCROLL_FLAG_SNAP_MARGINS}. Otherwise, use {@link #SCROLL_FLAG_NO_SCROLL} to disable
         *              scrolling.
         * @see #getScrollFlags()
         */
        public void setScrollFlags(int flags) {
            scrollFlags = flags;
        }

        /**
         * Returns the scrolling flags.
         *
         * @see #setScrollFlags(int)
         */
        public int getScrollFlags() {
            return scrollFlags;
        }

        /**
         * Set the interpolator to when scrolling the view associated with this
         * {@link LayoutParams}.
         *
         * @param interpolator the interpolator to use, or null to use normal 1-to-1 scrolling.
         * @see #getScrollInterpolator()
         */
        public void setScrollInterpolator(Interpolator interpolator) {
            scrollInterpolator = interpolator;
        }

        /**
         * Returns the {@link Interpolator} being used for scrolling the view associated with this
         * {@link LayoutParams}. Null indicates 'normal' 1-to-1 scrolling.
         *
         * @see #setScrollInterpolator(Interpolator)
         */
        public Interpolator getScrollInterpolator() {
            return scrollInterpolator;
        }

        /**
         * Returns true if the scroll flags are compatible for 'collapsing'
         */
        boolean isCollapsible() {
            return (scrollFlags & SCROLL_FLAG_SCROLL) == SCROLL_FLAG_SCROLL
                    && (scrollFlags & COLLAPSIBLE_FLAGS) != 0;
        }
    }

    /**
     * The default {@link Behavior} for {@link AppBarLayout}. Implements the necessary nested scroll
     * handling with offsetting.
     */
    public static class Behavior extends BaseBehavior<AppBarLayout> {

        /**
         * Callback to allow control over any {@link AppBarLayout} dragging.
         */
        public abstract static class DragCallback extends BaseBehavior.BaseDragCallback<AppBarLayout> {
        }

        public Behavior() {
            super();
        }

        public Behavior(Context context, AttrSet attrs) {
            super(context, attrs);
        }
    }

    /**
     * The default {@link Behavior} for {@link AppBarLayout}. Implements the necessary nested scroll
     * handling with offsetting.
     */
    protected static class BaseBehavior<T extends AppBarLayout> extends HeaderBehavior<T> {
        private static final int MAX_OFFSET_ANIMATION_DURATION = 600; // ms
        private static final int INVALID_POSITION = -1;

        /**
         * Callback to allow control over any {@link AppBarLayout} dragging.
         */
        public abstract static class BaseDragCallback<T extends AppBarLayout> {
            /**
             * Allows control over whether the given {@link AppBarLayout} can be dragged or not.
             *
             * <p>Dragging is defined as a direct touch on the AppBarLayout with movement. This call does
             * not affect any nested scrolling.
             *
             * @return true if we are in a position to scroll the AppBarLayout via a drag, false if not.
             */
            public abstract boolean canDrag(T appBarLayout);
        }

        private int offsetDelta;

        private int lastStartedType;

//        private boolean mSkipNestedPreScroll;
//        private boolean mWasNestedFlung;

        private AnimatorValue offsetAnimator;

        private int offsetToChildIndexOnLayout = INVALID_POSITION;
        private boolean offsetToChildIndexOnLayoutIsMinHeight;
        private float offsetToChildIndexOnLayoutPerc;

        private WeakReference<Component> lastNestedScrollingChildRef;
        private BaseDragCallback onDragCallback;

        public BaseBehavior() {
        }

        public BaseBehavior(Context context, AttrSet attrs) {
            super(context, attrs);
        }

        @Override
        public boolean onStartNestedScroll(CoordinatorLayout parent, T child,
                                           Component directTargetChild, Component target, int nestedScrollAxes, int type) {
            // Return true if we're nested scrolling vertically, and we have scrollable children
            // and the scrolling view is big enough to scroll
            final boolean started = (nestedScrollAxes & NestedScrollingHelper.SCROLL_AXIS_VERTICAL) != 0
                    && canScrollChildren(parent, child, directTargetChild);

            if (started && offsetAnimator != null) {
                // Cancel any offset animation
                offsetAnimator.cancel();
            }

            // A new nested scroll has started so clear out the previous ref
            lastNestedScrollingChildRef = null;

            // Track the last started type so we know if a fling is about to happen once scrolling ends
            lastStartedType = type;
            return started;
        }

        private boolean canScrollChildren(CoordinatorLayout parent, T child, Component directTargetChild) {
            return child.hasScrollableChildren()
                    && parent.getHeight() - directTargetChild.getHeight() <= child.getHeight();
        }

        @Override
        public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, T child,
                                      Component target, int dx, int dy, int[] consumed, int type) {
            if (dy != 0) {
                int min;
                int max;
                if (dy < 0) {
                    // We're scrolling down
                    min = -child.getTotalScrollRange();
                    max = min + child.getDownNestedPreScrollRange();
                } else {
                    // We're scrolling up
                    min = -child.getUpNestedPreScrollRange();
                    max = 0;
                }
                if (min != max) {
                    consumed[1] = scroll(coordinatorLayout, child, dy, min, max);
                }
            }
//            if (child.isLiftOnScroll()) {
//                child.setLiftedState(child.shouldLift(target));
//            }
        }

        @Override
        public void onNestedScroll(CoordinatorLayout coordinatorLayout, T child,
                                   Component target, int dxConsumed, int dyConsumed,
                                   int dxUnconsumed, int dyUnconsumed, int type, int[] consumed) {
            if (dyUnconsumed < 0) {
                // If the scrolling view is scrolling down but not consuming, it's probably be at
                // the top of it's content
                consumed[1] =
                        scroll(coordinatorLayout, child, dyUnconsumed, -child.getDownNestedScrollRange(), 0);
            }

//            if (dyUnconsumed == 0) {
//                // The scrolling view may scroll to the top of its content without updating the actions, so
//                // update here.
//                updateAccessibilityActions(coordinatorLayout, child);
//            }
        }

        @Override
        public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, T abl,
                                       Component target, int type) {
            if (lastStartedType == NestedScrollingHelper.TYPE_TOUCH || type == NestedScrollingHelper.TYPE_NON_TOUCH) {
                // If we haven't been flung, or a fling is ending
                snapToChildIfNeeded(coordinatorLayout, abl);
//                if (abl.isLiftOnScroll()) {
//                    abl.setLiftedState(abl.shouldLift(target));
//                }
            }

            // Keep a reference to the previous nested scrolling child
            lastNestedScrollingChildRef = new WeakReference<>(target);
        }

//        @Override
//        public boolean onNestedFling(final CoordinatorLayout coordinatorLayout,
//                                     final T child, Component target, float velocityX, float velocityY,
//                                     boolean consumed) {
//            boolean flung = false;
//
//            if (!consumed) {
//                // It has been consumed so let's fling ourselves
//                flung = fling(coordinatorLayout, child, -child.getTotalScrollRange(),
//                        0, -velocityY);
//            } else {
//                // If we're scrolling up and the child also consumed the fling. We'll fake scroll
//                // up to our 'collapsed' offset
//                if (velocityY < 0) {
//                    // We're scrolling down
//                    final int targetScroll = -child.getTotalScrollRange()
//                            + child.getDownNestedPreScrollRange();
//                    if (getTopBottomOffsetForScrollingSibling() < targetScroll) {
//                        // If we're currently not expanded more than the target scroll, we'll
//                        // animate a fling
//                        animateOffsetTo(coordinatorLayout, child, targetScroll, velocityY);
//                        flung = true;
//                    }
//                } else {
//                    // We're scrolling up
//                    final int targetScroll = -child.getUpNestedPreScrollRange();
//                    if (getTopBottomOffsetForScrollingSibling() > targetScroll) {
//                        // If we're currently not expanded less than the target scroll, we'll
//                        // animate a fling
//                        animateOffsetTo(coordinatorLayout, child, targetScroll, velocityY);
//                        flung = true;
//                    }
//                }
//            }
//
//            mWasNestedFlung = flung;
//            return flung;
//        }

        /**
         * Set a callback to control any {@link AppBarLayout} dragging.
         *
         * @param callback the callback to use, or {@code null} to use the default behavior.
         */
        public void setDragCallback(BaseDragCallback callback) {
            onDragCallback = callback;
        }

        private void animateOffsetTo(final CoordinatorLayout coordinatorLayout,
                                     final T child, final int offset, float velocity) {
            final int distance = Math.abs(getTopBottomOffsetForScrollingSibling() - offset);

            final int duration;
            velocity = Math.abs(velocity);
            if (velocity > 0) {
                duration = 3 * Math.round(1000 * (distance / velocity));
            } else {
                final float distanceRatio = (float) distance / child.getHeight();
                duration = (int) ((distanceRatio + 1) * 150);
            }

            animateOffsetWithDuration(coordinatorLayout, child, offset, duration);
        }

        private void animateOffsetWithDuration(final CoordinatorLayout coordinatorLayout,
                                               final T child, final int offset, final int duration) {
            final int currentOffset = getTopBottomOffsetForScrollingSibling();
            if (currentOffset == offset) {
                if (offsetAnimator != null && offsetAnimator.isRunning()) {
                    offsetAnimator.cancel();
                }
                return;
            }

            if (offsetAnimator == null) {
                offsetAnimator = new AnimatorValue();
                // TODO mOffsetAnimator Interpolator
//                mOffsetAnimator.setInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
            } else {
                offsetAnimator.cancel();
            }

            offsetAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float v) {
                    setHeaderTopBottomOffset(coordinatorLayout, child, calculateAnimateOffset(currentOffset, offset, v));
                }
            });

            offsetAnimator.setDuration(Math.min(duration, MAX_OFFSET_ANIMATION_DURATION));
//            mOffsetAnimator.setIntValues(currentOffset, offset);
            offsetAnimator.start();
        }

        private int calculateAnimateOffset(int current, int offset, float fraction) {
            return current + (int) ((offset - current) * fraction);
        }

        private int getChildIndexOnOffset(T abl, final int offset) {
            for (int i = 0, count = abl.getChildCount(); i < count; i++) {
                Component child = abl.getComponentAt(i);
                int top = child.getTop();
                int bottom = child.getBottom();

                final LayoutParams lp = (LayoutParams) child.getLayoutConfig();
                if (checkFlag(lp.getScrollFlags(), LayoutParams.SCROLL_FLAG_SNAP_MARGINS)) {
                    // Update top and bottom to include margins
                    top -= lp.getMarginTop();
                    bottom += lp.getMarginBottom();
                }

                if (top <= -offset && bottom >= -offset) {
                    return i;
                }
            }
            return -1;
        }

        private void snapToChildIfNeeded(CoordinatorLayout coordinatorLayout, T abl) {
            final int offset = getTopBottomOffsetForScrollingSibling();
            final int offsetChildIndex = getChildIndexOnOffset(abl, offset);
            if (offsetChildIndex >= 0) {
                final Component offsetChild = abl.getComponentAt(offsetChildIndex);
                final LayoutParams lp = (LayoutParams) offsetChild.getLayoutConfig();
                final int flags = lp.getScrollFlags();

                if ((flags & LayoutParams.FLAG_SNAP) == LayoutParams.FLAG_SNAP) {
                    // We're set the snap, so animate the offset to the nearest edge
                    int snapTop = -offsetChild.getTop();
                    int snapBottom = -offsetChild.getBottom();

//                    if (offsetChildIndex == abl.getChildCount() - 1) {
//                        // If this is the last child, we need to take the top inset into account
//                        snapBottom += abl.getTopInset();
//                    }

                    if (checkFlag(flags, LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED)) {
                        // If the view is set only exit until it is collapsed, we'll abide by that
                        snapBottom += offsetChild.getMinHeight();
                    } else if (checkFlag(
                            flags, LayoutParams.FLAG_QUICK_RETURN | LayoutParams.SCROLL_FLAG_ENTER_ALWAYS)) {
                        // If it's set to always enter collapsed, it actually has two states. We
                        // select the state and then snap within the state
                        final int seam = snapBottom + offsetChild.getMinHeight();
                        if (offset < seam) {
                            snapTop = seam;
                        } else {
                            snapBottom = seam;
                        }
                    }

                    if (checkFlag(flags, LayoutParams.SCROLL_FLAG_SNAP_MARGINS)) {
                        // Update snap destinations to include margins
                        snapTop += lp.getMarginTop();
                        snapBottom -= lp.getMarginBottom();
                    }

                    final int newOffset = offset < (snapBottom + snapTop) / 2 ? snapBottom : snapTop;
                    animateOffsetTo(
                            coordinatorLayout, abl, MathUtils.clamp(newOffset, -abl.getTotalScrollRange(), 0), 0);
                }
            }
        }

        private static boolean checkFlag(final int flags, final int check) {
            return (flags & check) == check;
        }

        @Override
        public boolean onMeasureChild(CoordinatorLayout parent, T child,
                                      int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec,
                                      int heightUsed) {
            final CoordinatorLayout.LayoutParams lp =
                    (CoordinatorLayout.LayoutParams) child.getLayoutConfig();
            if (lp.height == ComponentContainer.LayoutConfig.MATCH_CONTENT) {
                // If the view is set to wrap on it's height, CoordinatorLayout by default will
                // cap the view at the CoL's height. Since the AppBarLayout can scroll, this isn't
                // what we actually want, so we measure it ourselves with an unspecified spec to
                // allow the child to be larger than it's parent
                parent.onMeasureChild(
                        child,
                        parentWidthMeasureSpec,
                        widthUsed,
                        EstimateHelper.makeEstimateSpec(0, EstimateSpec.UNCONSTRAINT),
                        heightUsed);
                return true;
            }

            // Let the parent handle it as normal
            return super.onMeasureChild(
                    parent, child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
        }

        @Override
        public boolean onLayoutChild(CoordinatorLayout parent, T abl,
                                     LayoutDirection layoutDirection) {
            boolean handled = super.onLayoutChild(parent, abl, layoutDirection);

            // The priority for actions here is (first which is true wins):
            // 1. forced pending actions
            // 2. offsets for restorations
            // 3. non-forced pending actions
            final int pendingAction = abl.getPendingAction();
            if (offsetToChildIndexOnLayout >= 0 && (pendingAction & PENDING_ACTION_FORCE) == 0) {
                Component child = abl.getComponentAt(offsetToChildIndexOnLayout);
                int offset = -child.getBottom();
                if (offsetToChildIndexOnLayoutIsMinHeight) {
                    offset += child.getMinHeight(); // + abl.getTopInset()
                } else {
                    offset += Math.round(child.getHeight() * offsetToChildIndexOnLayoutPerc);
                }
                setHeaderTopBottomOffset(parent, abl, offset);
            } else if (pendingAction != PENDING_ACTION_NONE) {
                final boolean animate = (pendingAction & PENDING_ACTION_ANIMATE_ENABLED) != 0;
                if ((pendingAction & PENDING_ACTION_COLLAPSED) != 0) {
                    final int offset = -abl.getUpNestedPreScrollRange();
                    if (animate) {
                        animateOffsetTo(parent, abl, offset, 0);
                    } else {
                        setHeaderTopBottomOffset(parent, abl, offset);
                    }
                } else if ((pendingAction & PENDING_ACTION_EXPANDED) != 0) {
                    if (animate) {
                        animateOffsetTo(parent, abl, 0, 0);
                    } else {
                        setHeaderTopBottomOffset(parent, abl, 0);
                    }
                }
            }

            // Finally reset any pending states
            abl.resetPendingAction();
            offsetToChildIndexOnLayout = INVALID_POSITION;

            // We may have changed size, so let's constrain the top and bottom offset correctly,
            // just in case we're out of the bounds
            setTopAndBottomOffset(
                    MathUtils.clamp(getTopAndBottomOffset(), -abl.getTotalScrollRange(), 0));

            // Update the AppBarLayout's drawable state for any elevation changes. This is needed so that
            // the elevation is set in the first layout, so that we don't get a visual jump pre-N (due to
            // the draw dispatch skip)
            updateAppBarLayoutDrawableState(
                    parent, abl, getTopAndBottomOffset(), 0 /* direction */, true /* forceJump */);

            // Make sure we dispatch the offset update
            abl.onOffsetChanged(getTopAndBottomOffset());

//            updateAccessibilityActions(parent, abl);
            return handled;
        }

        @Override
        protected boolean canDragView(T view) {
            if (onDragCallback != null) {
                // If there is a drag callback set, it's in control
                return onDragCallback.canDrag(view);
            }

            // Else we'll use the default behaviour of seeing if it can scroll down
            if (lastNestedScrollingChildRef != null) {
                // If we have a reference to a scrolling view, check it
                final Component scrollingView = lastNestedScrollingChildRef.get();

                return scrollingView != null && scrollingView.getVisibility() != HIDE
                        && !scrollingView.canScroll(VERTICAL);
                // TODO check canScroll api
            } else {
                // Otherwise we assume that the scrolling view hasn't been scrolled and can drag.
                return true;
            }
        }

        @Override
        protected void onFlingFinished(CoordinatorLayout parent, T layout) {
            // At the end of a manual fling, check to see if we need to snap to the edge-child
            snapToChildIfNeeded(parent, layout);
//            if (layout.isLiftOnScroll()) {
//                layout.setLiftedState(layout.shouldLift(findFirstScrollingChild(parent)));
//            }
        }

        @Override
        protected int getMaxDragOffset(T view) {
            return -view.getDownNestedScrollRange();
        }

        @Override
        protected int getScrollRangeForDragFling(T view) {
            return view.getTotalScrollRange();
        }

        @Override
        protected int setHeaderTopBottomOffset(CoordinatorLayout coordinatorLayout,
                                               T appBarLayout, int newOffset, int minOffset, int maxOffset) {
            final int curOffset = getTopBottomOffsetForScrollingSibling();
            int consumed = 0;

            if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) {
                // If we have some scrolling range, and we're currently within the min and max
                // offsets, calculate a new offset
                newOffset = MathUtils.clamp(newOffset, minOffset, maxOffset);
                if (curOffset != newOffset) {
                    final int interpolatedOffset =
                            appBarLayout.hasChildWithInterpolator()
                                    ? interpolateOffset(appBarLayout, newOffset)
                                    : newOffset;

                    final boolean offsetChanged = setTopAndBottomOffset(interpolatedOffset);

                    // Update how much dy we have consumed
                    consumed = curOffset - newOffset;
                    // Update the stored sibling offset
                    offsetDelta = newOffset - interpolatedOffset;

                    if (!offsetChanged && appBarLayout.hasChildWithInterpolator()) {
                        // If the offset hasn't changed and we're using an interpolated scroll
                        // then we need to keep any dependent views updated. CoL will do this for
                        // us when we move, but we need to do it manually when we don't (as an
                        // interpolated scroll may finish early).
                        coordinatorLayout.dispatchDependentViewsChanged(appBarLayout);
                    }

                    // Dispatch the updates to any listeners
                    appBarLayout.onOffsetChanged(getTopAndBottomOffset());

                    // Update the AppBarLayout's drawable state (for any elevation changes)
                    updateAppBarLayoutDrawableState(
                            coordinatorLayout,
                            appBarLayout,
                            newOffset,
                            newOffset < curOffset ? -1 : 1,
                            false /* forceJump */);
                }
            } else {
                // Reset the offset delta
                offsetDelta = 0;
            }

//            updateAccessibilityActions(coordinatorLayout, appBarLayout);
            return consumed;
        }

        boolean isOffsetAnimatorRunning() {
            return offsetAnimator != null && offsetAnimator.isRunning();
        }

        private int interpolateOffset(T layout, final int offset) {
            final int absOffset = Math.abs(offset);

            for (int i = 0, z = layout.getChildCount(); i < z; i++) {
                final Component child = layout.getComponentAt(i);
                final AppBarLayout.LayoutParams childLp = (LayoutParams) child.getLayoutConfig();
                final Interpolator interpolator = childLp.getScrollInterpolator();

                if (absOffset >= child.getTop() && absOffset <= child.getBottom()) {
                    if (interpolator != null) {
                        int childScrollableHeight = 0;
                        final int flags = childLp.getScrollFlags();
                        if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
                            // We're set to scroll so add the child's height plus margin
                            childScrollableHeight += child.getHeight() + childLp.getMarginTop()
                                    + childLp.getMarginBottom();

                            if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
                                // For a collapsing scroll, we to take the collapsed height
                                // into account.
                                childScrollableHeight -= child.getMinHeight();
                            }
                        }

//                        if (ViewCompat.getFitsSystemWindows(child)) {
//                            childScrollableHeight -= layout.getTopInset();
//                        }

                        if (childScrollableHeight > 0) {
                            final int offsetForView = absOffset - child.getTop();
                            final int interpolatedDiff =
                                    Math.round(
                                            childScrollableHeight
                                                    * interpolator.getInterpolation(
                                                    offsetForView / (float) childScrollableHeight));

                            return Integer.signum(offset) * (child.getTop() + interpolatedDiff);
                        }
                    }

                    // If we get to here then the view on the offset isn't suitable for interpolated
                    // scrolling. So break out of the loop
                    break;
                }
            }

            return offset;
        }

        private void updateAppBarLayoutDrawableState(final CoordinatorLayout parent,
                                                     final AppBarLayout layout, final int offset, final int direction,
                                                     final boolean forceJump) {
            final Component child = getAppBarChildOnOffset(layout, offset);
            if (child != null) {
                final AppBarLayout.LayoutParams childLp = (LayoutParams) child.getLayoutConfig();
                final int flags = childLp.getScrollFlags();
                boolean lifted = false;

                if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
                    final int minHeight = child.getMinHeight();

                    if (direction > 0
                            && (flags
                            & (LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
                            | LayoutParams.SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED))
                            != 0) {
                        // We're set to enter always collapsed so we are only collapsed when
                        // being scrolled down, and in a collapsed offset
                        lifted = -offset >= child.getBottom() - minHeight; // - layout.getTopInset();
                    } else if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
                        // We're set to exit until collapsed, so any offset which results in
                        // the minimum height (or less) being shown is collapsed
                        lifted = -offset >= child.getBottom() - minHeight; // - layout.getTopInset();
                    }
                }

//                if (layout.isLiftOnScroll()) {
//                    // Use first scrolling child as default scrolling view for updating lifted state because
//                    // it represents the content that would be scrolled beneath the app bar.
//                    lifted = layout.shouldLift(findFirstScrollingChild(parent));
//                }

//                final boolean changed = layout.setLiftedState(lifted);

//                if (forceJump || (changed && shouldJumpElevationState(parent, layout))) {
//                    // If the collapsed state changed, we may need to
//                    // jump to the current state if we have an overlapping view
//                    layout.jumpDrawablesToCurrentState();
//                }
            }
        }

        private boolean shouldJumpElevationState(CoordinatorLayout parent, T layout) {
            // We should jump the elevated state if we have a dependent scrolling view which has
            // an overlapping top (i.e. overlaps us)
            final List<Component> dependencies = parent.getDependents(layout);
            for (int i = 0, size = dependencies.size(); i < size; i++) {
                final Component dependency = dependencies.get(i);
                final CoordinatorLayout.LayoutParams lp =
                        (CoordinatorLayout.LayoutParams) dependency.getLayoutConfig();
                final CoordinatorLayout.Behavior behavior = lp.getBehavior();

                if (behavior instanceof ScrollingViewBehavior) {
                    return ((ScrollingViewBehavior) behavior).getOverlayTop() != 0;
                }
            }
            return false;
        }

        private static Component getAppBarChildOnOffset(final AppBarLayout layout, final int offset) {
            final int absOffset = Math.abs(offset);
            for (int i = 0, z = layout.getChildCount(); i < z; i++) {
                final Component child = layout.getComponentAt(i);
                if (absOffset >= child.getTop() && absOffset <= child.getBottom()) {
                    return child;
                }
            }
            return null;
        }

        @Override
        protected int getTopBottomOffsetForScrollingSibling() {
            return getTopAndBottomOffset() + offsetDelta;
        }

        private Component findFirstScrollingChild(CoordinatorLayout parent) {
            for (int i = 0, z = parent.getChildCount(); i < z; i++) {
                final Component child = parent.getComponentAt(i);
                if (child instanceof NestedScrollingChild
                        || child instanceof ListContainer
                        || child instanceof ScrollView) {
                    return child;
                }
            }
            return null;
        }

        @Override
        public Sequenceable onSaveInstanceState(CoordinatorLayout parent, T abl) {
            final Sequenceable superState = super.onSaveInstanceState(parent, abl);
//            final int offset = getTopAndBottomOffset();

            // Try and find the first visible child...
//            for (int i = 0, count = abl.getChildCount(); i < count; i++) {
//                Component child = abl.getComponentAt(i);
//                final int visBottom = child.getBottom() + offset;
//
//                if (child.getTop() + offset <= 0 && visBottom >= 0) {
//                    final SavedState ss = new SavedState(superState);
//                    ss.firstVisibleChildIndex = i;
//                    ss.firstVisibleChildAtMinimumHeight =
//                            visBottom == (ViewCompat.getMinimumHeight(child) + abl.getTopInset());
//                    ss.firstVisibleChildPercentageShown = visBottom / (float) child.getHeight();
//                    return ss;
//                }
//            }

            // Else we'll just return the super state
            return superState;
        }

        @Override
        public void onRestoreInstanceState(CoordinatorLayout parent, T appBarLayout,
                                           Sequenceable state) {
//            if (state instanceof SavedState) {
//                final SavedState ss = (SavedState) state;
//                super.onRestoreInstanceState(parent, appBarLayout, ss.getSuperState());
//                mOffsetToChildIndexOnLayout = ss.firstVisibleChildIndex;
//                mOffsetToChildIndexOnLayoutPerc = ss.firstVisibleChildPercentageShown;
//                mOffsetToChildIndexOnLayoutIsMinHeight = ss.firstVisibleChildAtMinimumHeight;
//            } else {
//                super.onRestoreInstanceState(parent, appBarLayout, state);
//                mOffsetToChildIndexOnLayout = INVALID_POSITION;
//            }
        }

//        protected static class SavedState extends AbsSavedState {
//            int firstVisibleChildIndex;
//            float firstVisibleChildPercentageShown;
//            boolean firstVisibleChildAtMinimumHeight;
//
//            public SavedState(Parcel source, ClassLoader loader) {
//                super(source, loader);
//                firstVisibleChildIndex = source.readInt();
//                firstVisibleChildPercentageShown = source.readFloat();
//                firstVisibleChildAtMinimumHeight = source.readByte() != 0;
//            }
//
//            public SavedState(Parcelable superState) {
//                super(superState);
//            }
//
//            @Override
//            public void writeToParcel(Parcel dest, int flags) {
//                super.writeToParcel(dest, flags);
//                dest.writeInt(firstVisibleChildIndex);
//                dest.writeFloat(firstVisibleChildPercentageShown);
//                dest.writeByte((byte) (firstVisibleChildAtMinimumHeight ? 1 : 0));
//            }
//
//            public static final Parcelable.Creator<SavedState> CREATOR =
//                    ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
//                        @Override
//                        public SavedState createFromParcel(Parcel source, ClassLoader loader) {
//                            return new SavedState(source, loader);
//                        }
//
//                        @Override
//                        public SavedState[] newArray(int size) {
//                            return new SavedState[size];
//                        }
//                    });
//        }
    }

    /**
     * Behavior which should be used by {@link Component}s which can scroll vertically and support
     * nested scrolling to automatically scroll any {@link AppBarLayout} siblings.
     */
    public static class ScrollingViewBehavior extends HeaderScrollingViewBehavior {

        public ScrollingViewBehavior() {
        }

        public ScrollingViewBehavior(Context context, AttrSet attrs) {
            super(context, attrs);

//            final TypedArray a = context.obtainStyledAttributes(attrs,
//                    R.styleable.ScrollingViewBehavior_Layout);
//            setOverlayTop(a.getDimensionPixelSize(
//                    R.styleable.ScrollingViewBehavior_Layout_behavior_overlapTop, 0));
//            a.recycle();
            setOverlayTop(attrs.getAttr("behavior_overlapTop").map(Attr::getIntegerValue).orElse(0));
        }

        @Override
        public boolean layoutDependsOn(CoordinatorLayout parent, Component child, Component dependency) {
            // We depend on any AppBarLayouts
            return dependency instanceof AppBarLayout;
        }

        @Override
        public boolean onDependentViewChanged(CoordinatorLayout parent, Component child,
                                              Component dependency) {
            offsetChildAsNeeded(parent, child, dependency);
            return false;
        }

        @Override
        public boolean onRequestChildRectangleOnScreen(CoordinatorLayout parent, Component child,
                                                       Rect rectangle, boolean immediate) {
            final AppBarLayout header = findFirstDependency(parent.getDependencies(child));
            if (header != null) {
                // Offset the rect by the child's left/top
                rectangle.offset(child.getLeft(), child.getTop());

                final Rect parentRect = tempRect1;
                parentRect.set(0, 0, parent.getWidth(), parent.getHeight());

                if (!parentRect.contains(rectangle.left, rectangle.top, rectangle.right, rectangle.bottom)) {
                    // If the rectangle can not be fully seen the visible bounds, collapse
                    // the AppBarLayout
                    header.setExpanded(false, !immediate);
                    return true;
                }
            }
            return false;
        }

        private void offsetChildAsNeeded(CoordinatorLayout parent, Component child, Component dependency) {
            final CoordinatorLayout.Behavior behavior =
                    ((CoordinatorLayout.LayoutParams) dependency.getLayoutConfig()).getBehavior();
            if (behavior instanceof BaseBehavior) {
                // Offset the child, pinning it to the bottom the header-dependency, maintaining
                // any vertical gap and overlap
                final BaseBehavior ablBehavior = (BaseBehavior) behavior;
                Rect rect = child.getComponentPosition();
                ComponentPositionHelper.offsetTopAndBottom(child,
                        ((dependency.getComponentPosition().bottom - rect.top)
                                + ablBehavior.offsetDelta
                                + getVerticalLayoutGap()
                                - getOverlapPixelsForOffset(dependency)));

//                ViewCompat.offsetTopAndBottom(child, (dependency.getBottom() - child.getTop())
//                        + ablBehavior.mOffsetDelta
//                        + getVerticalLayoutGap()
//                        - getOverlapPixelsForOffset(dependency));
            }
        }

        @Override
        protected float getOverlapRatioForOffset(final Component header) {
            if (header instanceof AppBarLayout) {
                final AppBarLayout abl = (AppBarLayout) header;
                final int totalScrollRange = abl.getTotalScrollRange();
                final int preScrollDown = abl.getDownNestedPreScrollRange();
                final int offset = getAppBarLayoutOffset(abl);

                if (preScrollDown != 0 && (totalScrollRange + offset) <= preScrollDown) {
                    // If we're in a pre-scroll down. Don't use the offset at all.
                    return 0;
                } else {
                    final int availScrollRange = totalScrollRange - preScrollDown;
                    if (availScrollRange != 0) {
                        // Else we'll use a interpolated ratio of the overlap, depending on offset
                        return 1f + (offset / (float) availScrollRange);
                    }
                }
            }
            return 0f;
        }

        private static int getAppBarLayoutOffset(AppBarLayout abl) {
            final CoordinatorLayout.Behavior behavior =
                    ((CoordinatorLayout.LayoutParams) abl.getLayoutConfig()).getBehavior();
            if (behavior instanceof BaseBehavior) {
                return ((BaseBehavior) behavior).getTopBottomOffsetForScrollingSibling();
            }
            return 0;
        }

        @Override
        protected AppBarLayout findFirstDependency(List<Component> views) {
            for (int i = 0, z = views.size(); i < z; i++) {
                Component view = views.get(i);
                if (view instanceof AppBarLayout) {
                    return (AppBarLayout) view;
                }
            }
            return null;
        }

        @Override
        protected int getScrollRange(Component v) {
            if (v instanceof AppBarLayout) {
                return ((AppBarLayout) v).getTotalScrollRange();
            } else {
                return super.getScrollRange(v);
            }
        }
    }
}
